package cn.hd01.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.KeyStore;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.CharsetUtils;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;

import javax.net.ssl.SSLContext;

/**
 * @since 2015-7-15
 * @author wang_kun
 *
 */
public final class HttpClientHelper {
	private Logger logger = LoggerFactory.getLogger(HttpClientHelper.class);

	private static final int HTTP_CLIENT_SOTIMEOUT = 120000;
	private static final int HTTP_CLIENT_CONNECTIONTIMEOUT = 1000;
	private static final int HTTP_CLIENT_MAXTOTAL = 20000;
	private static final int HTTP_CLIENT_MAXPERROTE = 5000;

	private static SSLContext wx_ssl_context = null; // 微信支付ssl证书

	static {
		Resource resource = new ClassPathResource("wx_apiclient_cert.p12");
		try {
			KeyStore keystore = KeyStore.getInstance("PKCS12");
			char[] keyPassword = ConfigUtil.getProperty("mchid").toCharArray(); //证书密码 商户id
			keystore.load(resource.getInputStream(), keyPassword);
			wx_ssl_context = SSLContexts.custom().loadKeyMaterial(keystore, keyPassword).build();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static HttpClientHelper _instance = new HttpClientHelper();
	private CloseableHttpClient httpClient;
	private CloseableHttpClient httpsClient;

	private HttpClientHelper() {
		init();
	}

	public static HttpClientHelper instance() {
		return _instance;
	}

	private static SSLConnectionSocketFactory getSSLConnectionSocket(){
		return new SSLConnectionSocketFactory(wx_ssl_context, new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
	}

	private void init() {
		PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
		cm.setMaxTotal(HTTP_CLIENT_MAXTOTAL);
		cm.setDefaultMaxPerRoute(HTTP_CLIENT_MAXPERROTE);

		SocketConfig.Builder socketConfigBuilder = SocketConfig.copy(SocketConfig.DEFAULT);
		socketConfigBuilder.setTcpNoDelay(true);
		cm.setDefaultSocketConfig(socketConfigBuilder.build());

		HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
		RequestConfig defaultRequestConfig = RequestConfig.custom().setConnectTimeout(HTTP_CLIENT_CONNECTIONTIMEOUT)
				.setSocketTimeout(HTTP_CLIENT_SOTIMEOUT).setCookieSpec(CookieSpecs.IGNORE_COOKIES).build();

		httpClientBuilder.setDefaultRequestConfig(defaultRequestConfig).setConnectionManager(cm);

		httpClient = httpClientBuilder.build();
		httpsClient = httpClientBuilder.setSSLSocketFactory(getSSLConnectionSocket()).build();
	}

	public String getString(String url, Map<String, String> params) {
		url = urlBuild(url, params);
		logger.info("begin to get {}", url);

		CloseableHttpResponse response = null;
		try {
			response = httpClient.execute(new HttpGet(url));
			if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
				logger.error("response is {}", response.getStatusLine().getStatusCode());
				return null;
			}
			return EntityUtils.toString(response.getEntity(), CharsetUtils.get("UTF-8"));
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			closeQuietly(response);
			return null;
		}
	}

	public InputStream getStream(String url, Map<String, String> params) {
		url = urlBuild(url, params);
		logger.info("begin to get {}", url);

		CloseableHttpResponse response = null;
		try {
			response = httpClient.execute(new HttpGet(url));
			if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
				logger.error("response is {}", response.getStatusLine().getStatusCode());
				return null;
			}
			return response.getEntity().getContent();
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			closeQuietly(response);
			return null;
		}

	}

	public String post(String url, Map<String, String> params) {
		List<NameValuePair> uvp = new LinkedList<NameValuePair>();
		for (Entry<String, String> entry : params.entrySet()) {
			uvp.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
		}

		UrlEncodedFormEntity entity = null;

		try {
			entity = new UrlEncodedFormEntity(uvp, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			logger.error(e.getMessage(), e);
			return null;
		}

		HttpPost post = new HttpPost(url);
		post.setEntity(entity);

		CloseableHttpResponse response = null;
		try {
			logger.info("begin to post url {}, params {}", url, params);
			response = httpClient.execute(post);

			if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
				logger.info("response code is {}", response.getStatusLine().getStatusCode());
				return null;
			}

			return EntityUtils.toString(response.getEntity(), CharsetUtils.get("UTF-8"));
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			closeQuietly(response);
			return null;
		}
	}

	public String postJson(String url, String json, String auth) {
		HttpPost post = new HttpPost(url);
		post.addHeader("Content-Type", "application/json;charset=utf-8");
		post.addHeader("Authorization", auth);

		StringEntity entity = new StringEntity(json, "UTF-8");
		post.setEntity(entity);

		CloseableHttpResponse response = null;
		try {
			logger.info("begin to post url {}, params {}", url, json);
			response = httpClient.execute(post);

			if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
				logger.info("response code is {}", response.getStatusLine().getStatusCode());
				return null;
			}

			return EntityUtils.toString(response.getEntity(), CharsetUtils.get("UTF-8"));
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			closeQuietly(response);
			return null;
		}
	}

	public InputStream postXml(String url, String xml){
		HttpPost post = new HttpPost(url);
		post.addHeader("Content-Type", "application/xml;charset=utf-8");

		StringEntity entity = new StringEntity(xml, "UTF-8");
		post.setEntity(entity);

		CloseableHttpResponse response = null;
		try {
			logger.info("begin to post url {}, params {}", url, xml);
			response = httpClient.execute(post);

			if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
				logger.info("response code is {}", response.getStatusLine().getStatusCode());
				return null;
			}

			return response.getEntity().getContent();
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			closeQuietly(response);
			return null;
		}
	}

	public InputStream posts(String url, String data){
		HttpPost post = new HttpPost(url);
		StringEntity entity = new StringEntity(data, "UTF-8");
		post.setEntity(entity);

		CloseableHttpResponse response = null;
		try {
			logger.info("begin to posts url {}, params{}", url, data);
			response = httpsClient.execute(post);

			if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
				logger.info("response code is {}", response.getStatusLine().getStatusCode());
				return null;
			}

			return response.getEntity().getContent();
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			closeQuietly(response);
			return null;
		}
	}

	public void closeQuietly(CloseableHttpResponse response) {
		if (response == null) {
			return;
		}

		try {
			EntityUtils.consumeQuietly(response.getEntity());
			response.close();
		} catch (IOException e) {
			// ignor
		}
	}

	private String urlBuild(String url, Map<String, String> params) {
		if (params == null || params.isEmpty()) {
			return url;
		}

		StringBuffer sb = new StringBuffer();
		for (Entry<String, String> entry : params.entrySet()) {
			if (StringUtils.hasText(entry.getKey())) {
				sb.append(entry.getKey());
				sb.append("=");
				sb.append(URLCodecUtil.encode(entry.getValue()));
				sb.append("&");
			}
		}

		if (sb.length() == 0) {
			return url;
		}

		return url + "?" + sb.substring(0, sb.length() - 1);
	}
}
